package cn.mjnxin.iot.transfer;

import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.List;

import cn.mjnxin.iot.base.cache.CountCache;
import cn.mjnxin.iot.base.constant.ConstantValue;
import cn.mjnxin.iot.base.domain.ChannelData;
import cn.mjnxin.iot.base.domain.GatePacketWriter;
import cn.mjnxin.iot.base.domain.SocketData;
import cn.mjnxin.iot.base.log.LogWorker;
import cn.mjnxin.iot.base.util.CommonUtil;
import cn.mjnxin.iot.base.util.MixUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;

/**
 * 网关和前置的交互
 * 
 * @author CLG 2023年20月10日
 * @version V2.0.0
 */
public class TransferExchange{    
    /**
     * 向PID链里面的所有前置发送指定类型消息
     * @param type
     * @param gateNum
     * @param terminalAddress
     * @param pid
     * @return
     * @throws Exception
     */
    public static ByteBuf terminalLinkChange(int type, int gateNum, String terminalAddress, int pid, int logLevel) throws Exception { 
        // 连接序号默认1
        int count = CountCache.getCount();
        // 登录报文的真实报文长度为0（真实报文是指 以68开始 16结尾的报文）
        int len = 0;

        /**
         * 创建直接内存形式的ByteBuf，不能使用array()方法，但效率高
         */
        GatePacketWriter gateBuf = packHead(gateNum, type, terminalAddress, pid, logLevel, count, len);
        ByteBuf out = CommonUtil.getByteBuf(gateBuf.getPos());
        out.writeBytes(gateBuf.getDataBuffer());
        return out;
    }
    
    /**
     * 组装网关登录前置报文
     * @param gateNum
     * @param LocalAddress
     * @return
     * @throws Exception
     */
    public static ByteBuf gateLoginHost(int gateNum, String LocalAddress) throws Exception {
        // 连接序号默认1
        int count = CountCache.getInitCount();
        // 登录报文的真实报文长度为0（真实报文是指 以68开始 16结尾的报文）
        int len = 0;

        /**
         * 创建直接内存形式的ByteBuf，不能使用array()方法，但效率高
         */
        GatePacketWriter gateBuf = new GatePacketWriter(ConstantValue.GATE_HEAD_LENGTH);
        gateBuf.writeBytes(MixUtil.hex2bytes(ConstantValue.GATE_HEAD_DATA));
        gateBuf.writeInt32(len);// 整个长度
        gateBuf.writeInt8(new Integer(0x7F & ConstantValue.TYPE_GATE_LOGIN_HOST).byteValue());// type 03
        gateBuf.writeInt16(ConstantValue.TYPE_GATE_LOGIN_HOST);// protocolType
        gateBuf.writeInt8((byte) gateNum);// 网关编号

        final String[] arr = LocalAddress.split(ConstantValue.SPLIT_CHAR);
        byte[] bs = Inet4Address.getByName(arr[0]).getAddress();// 127.0.0.1 --> [127, 0, 0, 1]
        gateBuf.writeInt8(bs[0]);
        gateBuf.writeInt8(bs[1]);
        gateBuf.writeInt8(bs[2]);
        gateBuf.writeInt8(bs[3]);
        gateBuf.writeInt16(Integer.parseInt(arr[1]));// port 两个字节表示端口号
        gateBuf.writeInt16(count);// count 2个字节的count
        ByteBuf out = CommonUtil.getByteBuf(gateBuf.getPos());
        out.writeBytes(gateBuf.getDataBuffer());
        return out;
    }

    /**
     * 组装前置登录网关报文
     * @param isMngMode
     * @param gateNum
     * @param isAllMatch
     * @param pid
     * @param localAddress
     * @return
     * @throws Exception
     */
    public static ByteBuf hostLoginGate(boolean isMngMode, int gateNum, boolean isAllMatch, Integer pid, String localAddress) throws Exception {
        // 连接序号默认1
        int count = CountCache.getInitCount();
        // 登录报文的真实报文长度为0（真实报文是指 以68开始 16结尾的报文）
        int len = 0;

        /**
         * 创建直接内存形式的ByteBuf，不能使用array()方法，但效率高
         */
        GatePacketWriter gateBuf = new GatePacketWriter(ConstantValue.GATE_HEAD_LENGTH - 12);
        gateBuf.writeBytes(MixUtil.hex2bytes(ConstantValue.GATE_HEAD_DATA));
        gateBuf.writeInt32(len);// 整个长度
        if (isMngMode) {
            if (isAllMatch) {
                gateBuf.writeInt8(new Integer(0x7F & ConstantValue.TYPE_HOST_MNGALL_LOGIN).byteValue());// type 03 管理模式
                gateBuf.writeInt16(0x0000);// 无意义
            }
            else {
                gateBuf.writeInt8(new Integer(0x7F & ConstantValue.TYPE_HOST_MNGPID_LOGIN).byteValue());// type 03 管理模式
                gateBuf.writeInt16(pid);// protocolType
            }
        }
        else {
            if (isAllMatch) {
                gateBuf.writeInt8(new Integer(0x7F & ConstantValue.TYPE_ALL_HOST_LOGIN_GATE).byteValue());// type 00 报头类型
                gateBuf.writeInt16(0x0000);// 无意义
            }
            else {
                gateBuf.writeInt8(new Integer(0x7F & ConstantValue.TYPE_HOST_LOGIN_GATE).byteValue());// type 04 报头类型
                gateBuf.writeInt16(pid);// protocolType
            }
        }
        gateBuf.writeInt8((byte) gateNum);// 网关编号

        final String[] arr = localAddress.split(ConstantValue.SPLIT_CHAR);
        byte[] bs = Inet4Address.getByName(arr[0]).getAddress();// 127.0.0.1 --> [127, 0, 0, 1]
        gateBuf.writeInt8(bs[0]);
        gateBuf.writeInt8(bs[1]);
        gateBuf.writeInt8(bs[2]);
        gateBuf.writeInt8(bs[3]);
        gateBuf.writeInt16(Integer.parseInt(arr[1]));// port 两个字节表示端口号
        gateBuf.writeInt16(count);// 2个字节的count
        ByteBuf out = CommonUtil.getByteBuf(gateBuf.getPos());
        out.writeBytes(gateBuf.getDataBuffer());
        return out;
    }

    /**
     * 组装前置管理Client报文
     * @param gateNum
     * @param pid
     * @param terminalAddress
     * @param gateAddress
     * @return
     * @throws Exception
     */
    public static ByteBuf hostManageTerminal(int gateNum, Integer pid, int logLevel, String terminalAddress, String gateAddress) throws Exception { 
        // 连接序号默认1
        int count = CountCache.getCount();
        // 登录报文的真实报文长度为0（真实报文是指 以68开始 16结尾的报文）
        int len = 0;

        /**
         * 创建直接内存形式的ByteBuf，不能使用array()方法，但效率高
         */
        GatePacketWriter headBuf = packHead(gateNum, ConstantValue.TYPE_HOST_MNG_TERMINAL, terminalAddress, pid, logLevel, count, len);
        ByteBuf out = CommonUtil.getByteBuf(headBuf.getPos());
    	if (logLevel == 1) {
            LogWorker.log(String.format("%d|T[%s]: H->G[%s]=%d Bytes", pid, terminalAddress, gateAddress, headBuf.getDataBuffer().length));
    	}
    	else if (logLevel >= 2) {
            LogWorker.log(String.format("%d|T[%s]: H->G[%s]=", pid, terminalAddress, gateAddress), headBuf.getDataBuffer());
    	}
        out.writeBytes(headBuf.getDataBuffer());
        return out;
    }

    /**
     * 组装前置释放管理Client报文
     * @param gateNum
     * @param pid
     * @param terminalAddress
     * @param gateAddress
     * @return
     * @throws Exception
     */
    public static ByteBuf hostUnManageTerminal(int gateNum, Integer pid, int logLevel, String terminalAddress, String gateAddress) throws Exception { 
        // 连接序号默认1
        int count = CountCache.getCount();
        // 登录报文的真实报文长度为0（真实报文是指 以68开始 16结尾的报文）
        int len = 0;

        /**
         * 创建直接内存形式的ByteBuf，不能使用array()方法，但效率高
         */
        GatePacketWriter headBuf = packHead(gateNum, ConstantValue.TYPE_HOST_RLS_TERMINAL, terminalAddress, pid, logLevel, count, len);
        ByteBuf out = CommonUtil.getByteBuf(headBuf.getPos());
    	if (logLevel == 1) {
            LogWorker.log(String.format("%d|[%s]: H->G[%s]=%d Bytes", pid, terminalAddress, gateAddress, headBuf.getDataBuffer().length));
    	}
    	else if (logLevel >= 2) {
            LogWorker.log(String.format("%d|[%s]: H->G[%s]=", pid, terminalAddress, gateAddress), headBuf.getDataBuffer());
    	}
        out.writeBytes(headBuf.getDataBuffer());
        return out;
    }

    /**
     * 组装前置主动断连Client报文
     * @param gateNum
     * @param pid
     * @param terminalAddress
     * @param gateAddress
     * @return
     * @throws Exception
     */
    public static ByteBuf hostCloseTerminal(int gateNum, Integer pid, int logLevel, String terminalAddress, String gateAddress) throws Exception { 
        // 连接序号默认1
        int count = CountCache.getCount();
        // 登录报文的真实报文长度为0（真实报文是指 以68开始 16结尾的报文）
        int len = 0;

        /**
         * 创建直接内存形式的ByteBuf，不能使用array()方法，但效率高
         */
        GatePacketWriter headBuf = packHead(gateNum, ConstantValue.TYPE_HOST_CLS_TERMINAL, terminalAddress, pid, logLevel, count, len);
        ByteBuf out = CommonUtil.getByteBuf(headBuf.getPos());
    	if (logLevel == 1) {
            LogWorker.log(String.format("%d|[%s]: H->G=%d Bytes", pid, terminalAddress, headBuf.getDataBuffer().length));
    	}
    	else if (logLevel >= 2) {
            LogWorker.log(String.format("%d|[%s]: H->G=", pid, terminalAddress), headBuf.getDataBuffer());
    	}
        out.writeBytes(headBuf.getDataBuffer());
        return out;
    }

    /**
     * 封装报文头
     * @param gateNum
     * @param type
     * @param terminalAddress
     * @param pid
     * @param count
     * @param dataLen
     * @return
     * @throws Exception
     * @throws UnknownHostException
     */
    public static GatePacketWriter packHead(int gateNum, int type, String terminalAddress, int pid, int logLevel, int count, int dataLen)
            throws Exception, UnknownHostException {
        if (type < 0) {
            throw new RuntimeException("type不能为负数");
        }

        String[] arr = terminalAddress.split(ConstantValue.SPLIT_CHAR);
        String host = arr[0];
        final boolean isIPV4;
        isIPV4 = host.contains(".");
        final int len;
        if(isIPV4){
            len = ConstantValue.GATE_HEAD_LENGTH - 12;
        }
        else {
            len = ConstantValue.GATE_HEAD_LENGTH;
        }
        GatePacketWriter gateBuf= new GatePacketWriter(len); 
        gateBuf.writeBytes(MixUtil.hex2bytes(ConstantValue.GATE_HEAD_DATA)); // 报文头
        gateBuf.writeInt32(dataLen); // 整个业务报文长度
        
        // 协议类型（左侧起第一个bit为0 表示IPV4, 1表示IPV6 剩余7个bit表示协议类型编号）
        if(isIPV4){
            gateBuf.writeInt8((byte) (type & 0x7F)); // type
        }else{
            gateBuf.writeInt8((byte) (type & 0x7F | 0x80)); // type
        }
        gateBuf.writeInt16(pid);// protocolType
        
        gateBuf.writeInt8((byte) gateNum);//网关编号

        if(host.contains(".") || host.equals("localhost")){
            // 如果ip格式为IPV4，则少12个字节，反之，当前得12个byte+后续得4byte存放IPV6的值，存放顺序从左至右依次
            // 终端的IP地址，ip地址的每个段位占一个字节（不含符号和端口号）
            byte[] bs = Inet6Address.getByName(host).getAddress();
            gateBuf.writeInt8(bs[0]);
            gateBuf.writeInt8(bs[1]);
            gateBuf.writeInt8(bs[2]);
            gateBuf.writeInt8(bs[3]);
        }else{
            byte[] bs = Inet6Address.getByName(host).getAddress();
            for(int i = 0; i < 16; i++){
                gateBuf.writeInt8(bs[i]);
            }
        }

        gateBuf.writeInt16(Integer.parseInt(arr[1])); // 终端对应的端口号
        gateBuf.writeInt16(count);// 2个字节的count
        
        return gateBuf;
    }

    /**
     * 解包接口
     */
    public interface IUnpacker{
        /**
         * 解包一个报文
         * @param channel
         * @param channelData
         * @param type
         * @param isIPV4
         * @return
         */
        ChannelData unpackOnePackage(Channel channel, ChannelData channelData, int type, boolean isIPV4);
    }
    
    /**
     * 解码
     * @param ctx
     * @param in
     * @param out
     * @param unpacker
     * @throws Exception
     */
    public static void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out, IUnpacker unpacker, Integer logLevel) throws Exception {
        int length = ConstantValue.GATE_HEAD_LENGTH - 12;
        if(in.readableBytes() < length){
            return;
        }
        InetSocketAddress insocket = (InetSocketAddress)ctx.channel().remoteAddress();
        String ipAddress = CommonUtil.getIpAddress(insocket);
        while (true) {
            int readableBytes = in.readableBytes();//重新获取可读字段
            if(readableBytes < length){
                break;
            }
            int beginReader = in.readerIndex();//重新获取开始的index

            byte[] base = new byte[length];
            in.readBytes(base, 0, length);
            String baseHex = MixUtil.bytes2hex(base);
            int index = baseHex.indexOf(ConstantValue.GATE_HEAD_DATA);
            if (index < 0) { // 预读的一段无法识别，丢弃
                System.out.println(baseHex);
                continue;
            }
            else { // 找到识别串
                beginReader = beginReader + index / 2;
                in.readerIndex(beginReader);
                readableBytes = in.readableBytes();//重新获取可读字段
            }

            if(readableBytes < length){
                //报文不完整 还原
                in.readerIndex(beginReader);
                break;
            }

            in.skipBytes(ConstantValue.GATE_HEAD_DATA_LEN);
            //获取到网关头长度
            int socketDataLen = in.readIntLE(); // 长度域：真实报文长度，包含业务报文的报头和报尾
            int type = in.readByte(); // 报头类型
            boolean isIPV4 = (type >> 7 & 1) == 0    ? true : false;    
            type = type & 0x7f; // type
            if (isIPV4) {
                if(in.readableBytes() < (socketDataLen + ConstantValue.GATE_HEAD_LENGTH - 17 - ConstantValue.GATE_HEAD_DATA_LEN) ){
                    // 报文不完整
                    in.readerIndex(beginReader);
                    break;
                }
            }
            else {
                if(in.readableBytes() < (socketDataLen + ConstantValue.GATE_HEAD_LENGTH - 5 - ConstantValue.GATE_HEAD_DATA_LEN) ){
                    // 报文不完整
                    in.readerIndex(beginReader);
                    break;
                }
            }
            //报文完整
            ChannelData originData = TransferExchange.unpackChannelData(in, socketDataLen, type, isIPV4, ipAddress);

            if (LogWorker.isLogable()) {
                in.readerIndex(beginReader);
                final byte[] dst;
                if (isIPV4) {
                    dst = new byte[ConstantValue.GATE_HEAD_LENGTH + socketDataLen - 12];
                }
                else {
                    dst = new byte[ConstantValue.GATE_HEAD_LENGTH + socketDataLen];
                }
                
                in.readBytes(dst);
                if (type == ConstantValue.TYPE_GATE_LOGIN_HOST) {
                	if (logLevel == 1) {
                        LogWorker.logForce(String.format("%d|G[%s]->H=%d Bytes", originData.getPid(), originData.getGateAddress(), dst.length));
                	}
                	else if (logLevel >= 2) {
                        LogWorker.logForce(String.format("%d|G[%s]->H=", originData.getPid(), originData.getGateAddress()), dst);
                	}
                }
                else if(type == ConstantValue.TYPE_HOST_LOGIN_GATE
                        || type == ConstantValue.TYPE_HOST_MNGALL_LOGIN
                        || type == ConstantValue.TYPE_HOST_MNGPID_LOGIN
                        || type == ConstantValue.TYPE_HOST_MNG_TERMINAL
                        || type == ConstantValue.TYPE_HOST_RLS_TERMINAL
                        || type == ConstantValue.TYPE_ALL_HOST_LOGIN_GATE) {
                	if (logLevel == 1) {
                        LogWorker.logForce(String.format("%d|H[%s]->G=%d Bytes", originData.getPid(), originData.getHostAddress(), dst.length));
                	}
                	else if (logLevel >= 2) {
                        LogWorker.logForce(String.format("%d|H[%s]->G=", originData.getPid(), originData.getHostAddress()), dst);
                	}
                }
                else if (type == ConstantValue.TYPE_TERMINAL_LINK
                        || type == ConstantValue.TYPE_TERMINAL_UNLINK
                        || type == ConstantValue.TYPE_TERMINAL_CLOSED
                        || type == ConstantValue.TYPE_TERMINAL_TIMEOUT) {
                	if (logLevel == 1) {
                		LogWorker.log(String.format("%d|T[%s]: G[%s]->H=%d Bytes", originData.getPid(), originData.getTerminalAddress(), originData.getGateAddress(), dst.length));
                	}
                	else if (logLevel >= 2) {
                		LogWorker.log(String.format("%d|T[%s]: G[%s]->H=", originData.getPid(), originData.getTerminalAddress(), originData.getGateAddress()), dst);
                	}
                }
                else if (type == ConstantValue.TYPE_TERMINAL_TO_HOST) {
                	if (logLevel == 1) {
                		LogWorker.log(String.format("%d|T[%s]->G[%s]->H=%d Bytes", originData.getPid(), originData.getTerminalAddress(), originData.getGateAddress(), dst.length));
                	}
                	else if (logLevel >= 2) {
                		LogWorker.log(String.format("%d|T[%s]->G[%s]->H=", originData.getPid(), originData.getTerminalAddress(), originData.getGateAddress()), dst);
                	}
                }
                else if (type == ConstantValue.TYPE_HOST_TO_TERMINAL) {
                	if (logLevel == 1) {
                		LogWorker.log(String.format("%d|H[%s]->G->T[%s]=%d Bytes", originData.getPid(), originData.getHostAddress(), originData.getTerminalAddress(), dst.length));
                	}
                	else if (logLevel >= 2) {
                		LogWorker.log(String.format("%d|H[%s]->G->T[%s]=", originData.getPid(), originData.getHostAddress(), originData.getTerminalAddress()), dst);
                	}
                }
                else if (type == ConstantValue.TYPE_HOST_CLS_TERMINAL) {
                	if (logLevel == 1) {
                		LogWorker.log(String.format("%d|T[%s]: H[%s]->G=%d Bytes", originData.getPid(), originData.getTerminalAddress(), originData.getHostAddress(), dst.length));
                	}
                	else if (logLevel >= 2) {
                		LogWorker.log(String.format("%d|T[%s]: H[%s]->G=", originData.getPid(), originData.getTerminalAddress(), originData.getHostAddress()), dst);
                	}
                }
                else {
                    LogWorker.logForce(String.format("%d|Unkown=", originData.getPid()), dst);
                }
            }
            
            ChannelData channelData = unpacker.unpackOnePackage(ctx.channel(), originData, type, isIPV4);
            if (channelData != null) {
                out.add(channelData);
            }
        }
    }
    
    /**
     * 解包
     * @param in
     * @param socketDataLen
     * @param type
     * @param isIPV4
     * @param channelRemoteAddress
     * @return
     */
    private static ChannelData unpackChannelData(ByteBuf in, int socketDataLen, int type, boolean isIPV4, String channelRemoteAddress) {
        int pid = in.readShortLE();  // 2字节协议类型编号
        int gateNum = in.readByte();// 网关编号
        
        StringBuilder ipAddress = new StringBuilder();
        if(isIPV4){
            ipAddress.append(in.readByte()&0xFF);  // ip地址需要转成10进制数
            ipAddress.append(".");
            ipAddress.append(in.readByte()&0xFF);
            ipAddress.append(".");
            ipAddress.append(in.readByte()&0xFF);
            ipAddress.append(".");
            ipAddress.append(in.readByte()&0xFF);
        }else{
            byte[] dataTemp = new byte[16]; // 当前得12个byte+后续得4byte存放IPV6的值，存放顺序从左至右依次
            for(int i = 0 ; i < 16 ;i++){ // 终端的IP地址，ip地址的每个段位占一个字节（不含符号和端口号）
                dataTemp[i] = in.readByte();
            }
            try {
                ipAddress.append(Inet6Address.getByAddress(dataTemp).getHostAddress());
            } catch (UnknownHostException e) {
                e.printStackTrace();
            }
        }
        ipAddress.append(ConstantValue.CONNECTOR_CHAR);
        ipAddress.append(in.readShortLE() & 0xFFFF); // 终端对应的端口号
        int count =  in.readShortLE();// 前置与网关建立连接时对应的连接序号（1-10000循环） 
        String address = ipAddress.toString();

        ChannelData channelData = new ChannelData(pid, null);
        channelData.setGateNum(gateNum);
        if (type == ConstantValue.TYPE_GATE_LOGIN_HOST) {
            channelData.setGateAddress(channelRemoteAddress);
        }
        else if(type == ConstantValue.TYPE_HOST_LOGIN_GATE
                || type == ConstantValue.TYPE_HOST_MNGALL_LOGIN
                || type == ConstantValue.TYPE_HOST_MNGPID_LOGIN
                || type == ConstantValue.TYPE_ALL_HOST_LOGIN_GATE) {
            channelData.setHostAddress(channelRemoteAddress);
        }
        else if (type == ConstantValue.TYPE_TERMINAL_LINK
                || type == ConstantValue.TYPE_TERMINAL_UNLINK
                || type == ConstantValue.TYPE_TERMINAL_CLOSED
                || type == ConstantValue.TYPE_TERMINAL_TIMEOUT) {
        	channelData.setGateAddress(channelRemoteAddress);
            channelData.setTerminalAddress(address);
        }
        else if (type == ConstantValue.TYPE_TERMINAL_TO_HOST) {
        	channelData.setGateAddress(channelRemoteAddress);
            channelData.setTerminalAddress(address);
        }
        else if (type == ConstantValue.TYPE_HOST_TO_TERMINAL
        		|| type == ConstantValue.TYPE_HOST_CLS_TERMINAL
                || type == ConstantValue.TYPE_HOST_MNG_TERMINAL
                || type == ConstantValue.TYPE_HOST_RLS_TERMINAL) {
            channelData.setHostAddress(channelRemoteAddress);
            channelData.setTerminalAddress(address);
        }
        else {
        	// TODO:???
            channelData.setTerminalAddress(address);
        }
        channelData.setType(type); // 包类型
        channelData.setGateNum(gateNum); // 网关编号
        channelData.setIPV4(isIPV4); // 是否IPV4
        channelData.setPid(pid); // 协议类型
        if (socketDataLen > 0) {
            ByteBuf readBytes = in.readBytes(socketDataLen);
            SocketData data = new SocketData(readBytes);
            data.setCount(count);
            data.setPid(pid); // 协议类型
            channelData.setSocketData(data);
        }
        return channelData;
    }
}